2012年10月24日 星期三

[懷舊] 聊聊 svgalib

svgalib 是我想在 linux console 裡頭使用繪圖模式時候找到的 library。在很久很久以前 X 還沒有現在這麼好用, 我有很長一段時間是在 linux console 下作業 - 主要是寫程式。而想要練習在圖型介面下的程式, 類似 turbo c 的 bgi 或是 bios call 0x10 就會需要 svgalib。

那時候找了一些 linux console graphic library, ggi, Allegro, 當然還有 linux framebuffer, 不過最後我還是用了 svgalib, 至於為什麼不用相容性更好的  linux framebuffer? 因為我想要有文字模式, 我只想要在我需要的時候才切換到繪圖模式, linux framebuffer 不知道為什麼一定要在開機就決定, 不能在需要的時候才載入? 有什麼技術上的問題嗎?我似乎搞錯了, 有兩種模式, 開機時指定 vga 參數, 以及開機後手動載入 fb module, 不過一旦 載入 fb module 就無法移除了。但是在 arm/linux 上, 就只能選擇 linux framebuffer了。

jmcce 中文終端機就是用這個寫的 (也支援 linux framebuffer), 我也曾經一度很迷中文終端機的程式, 不過在 X 環境愈來愈好用的現在, 已經沒這麼迫切的需求了。

svgalib 似乎沒在維護了, 在新的顯示卡上, 無法使用最新功能, 不過沒關係, 只要使用 vga 或是 vesa driver 就可以有基本的顯示效果, 而這也應該是所有顯示卡都會支援的模式。

安裝 svgalib
apt-get install libsvga1-dev

編輯 /etc/vga/libvga.config, 不編輯也沒關係, svgalib 會自動偵測

/etc/vga/libvga.config
  1 # Configuration file for svgalib. Default location is /etc/vga.
  2 # Other config file locations: ~/.svgalibrc
  3 #     where SVGALIB_CONFIG_FILE points
  4 # Lines starting with '#' are ignored.
  5 
  6 # If you have two vga cards with the same pci vendor id, svgalib will try
  7 # to use the first one, even if the second one is active. In that case,
  8 # use PCIStart to force starting the search for a vga card only at a
  9 # specific bus and device numbers. For example, an AGP card is usually on
 10 # bus 1, while pci is on bus 0, so to use the AGP card, uncomment:
 11 
 12 # PCIStart 1 0
 13 
 14 # Have a deep look at README.config to see what can do here (especially
 15 # for mach32).
 16 
 17 # Mouse type:
 18 
 19 # mouse Microsoft # Microsoft
 20 # mouse MouseSystems # Mouse Systems
 21 # mouse MMSeries # Logitech MM Series
 22 # mouse Logitech # Logitech protocol (old, newer mice use Microsoft protocol)
 23 # mouse Busmouse # Bus mouse
 24 # mouse PS2  # PS/2 mouse
 25 # mouse MouseMan # Logitech MouseMan
 26 # mouse Spaceball # Spacetec Spaceball
 27 # mouse IntelliMouse # Microsoft IntelliMouse or Logitech MouseMan+ on serial port
 28 # mouse IMPS2  # Microsoft IntelliMouse or Logitech MouseMan+ on PS/2 port
 29 # mouse pnp  # plug'n'pray
 30 # mouse WacomGraphire   # Wacom Graphire tablet/mouse
 31 # mouse DRMOUSE4DS # Digital Research double-wheeled mouse
 32 # mouse none  # None
 33 
 34 mouse unconfigured
 35 
 36 # (DEBIAN NOTE: the mouse used to default to microsoft, but this was changed
 37 #  to fix bug #13458. If your mouse used to work fine, you can simply change
 38 #  it back to read "microsoft" again. If you are careful to change *only that
 39 #  one word*, and not to add or remove extra whitespace, the package
 40 #  installation will continue to update this file without requiring user
 41 #  intervention because of a modified config file.
 42 #  This applies to all mouse types, not just microsoft.)
 43 
 44 # Mouse/keyboard customisation by 101 (Attila Lendvai). If you have any good
 45 # ideas you can reach me at 101@kempelen.inf.bme.hu
 46 
 47 # mouse_accel_type normal # No acceleration while delta is less then
 48     # threshold but delta is multiplied by
 49     # mouse_accel_mult if more. Originally done by
 50     # Mike Chapman mike@paranoia.com
 51 
 52 mouse_accel_type power # The acceleration factor is a power function
 53     # of delta until it reaches m_accel_mult. It
 54     # starts from the coordinate 
 55     # [1, 1 + m_accel_offset] and goes to
 56     # [m_accel_thresh, m_accel_mult]. If delta
 57     # is bigger then m_accel_thresh it is a plain
 58     # constant (m_accel_mult). It is the f(delta)
 59     # function with which the delta itself will be
 60     # multiplied. m_accel_offset is 1 by default,
 61     # so for delta = 1 the accelerated delta will
 62     # remain 1 (You don't lose resolution). The
 63     # starting point of the f(delta) function
 64     # might be moved along the Y axis up/down with
 65     # m_accel_offset thus defining the initial
 66     # minimum acceleration (for delta = 1).
 67     # Basically it's like the normal mode but the
 68     # acceleration factor grows as you move your
 69     # mouse faster and faster, not just turns in
 70     # and out. Threshold is the point from where
 71     # the f(delta) function gets linear.
 72     # This is the one I use for *uaking... =)
 73 
 74 # mouse_accel_type off # No comment...
 75 
 76 
 77 mouse_accel_mult 60 # This is the number with which delta will
 78     # be multiplied. Basically it's the number
 79     # that defines how big the acceleration will
 80     # be
 81 
 82 mouse_accel_thresh 4 # This is the threshold. See description by
 83     # power
 84 
 85 mouse_accel_power 0.8 # This is the second parameter of the power
 86     # function used in power mode. Used only by
 87     # the power mode
 88 
 89 mouse_accel_offset 30 # This is the offset of the starting point
 90     # on the Y axis. With this you can define the
 91     # number that will multiply delta = 1 so it's
 92     # the initial acceleration.
 93 
 94 # mouse_accel_maxdelta 600 # This is an upper limit for delta after
 95     # the acceleration was applied
 96 
 97 # mouse_maxdelta 30 # This is an upper limit for the delta
 98     # before the acceleration. With this one you
 99     # can limit the biggest valid delta that
100     # comes from the mouse.
101 
102 # mouse_force   # Force parameters even if they seem strange
103     # By default svgalib prints an error if any
104     # of the numbers are somhow out of the
105     # reasonable limit, (Like a negative mult :)
106     # and uses the default that's in vgamouse.h
107 
108 # The default device is /dev/input/mice.
109 # However, esp. with the Spacetec Spaceball you may
110 # want to specify a different device for svgalib to use
111 
112 # mdev /dev/ttyS0 # mouse is at /dev/ttyS0
113 
114 # Some multiprotocol mice will need one of the following:
115 
116 # setRTS   # set the RTS wire.
117 # clearRTS # clear the RTS wire.
118 # leaveRTS # leave the RTS wire alone (default) (Wire is usually set)
119 # setDTR   # set the DTR wire.
120 # clearDTR # clear the DTR wire.
121 # leaveDTR # leave the DTR wire alone (default) (Wire is usually set)
122 
123 # On mice such as the Microsoft IntelliMouse and Logitech MouseMan+, turning
124 # the wheel rotates the mouse around the X axis. mouse_wheel_steps controls
125 # how many steps make up a full 360-degree turn and thus how much rotation
126 # occurs with each step. The default is 18 steps (20 degrees per step), the
127 # real-world value for the IntelliMouse. Adjust it to match your mouse or to
128 # suit your preferences; a negative number reverses the direction and zero
129 # disables rotation.
130 
131 mouse_wheel_steps 18  # For MS IntelliMouse (default)
132 # mouse_wheel_steps 24  # For Logitech FirstMouse+
133 # mouse_wheel_steps -18  # Reverses direction
134 # mouse_wheel_steps 0  # Disables rotation
135 
136 # mouse_fake_kbd_event sends a fake keyboard event to the program when the
137 # wheel on a Microsoft IntelliMouse, Logitech MouseMan+, or similar wheel
138 # mouse is turned. This can be useful for programs that do not recognize the
139 # Z axis, but only works with some programs that use raw keyboard.
140 # The format is:
141 #
142 #   mouse_fake_kbd_event upscancode downscancode
143 #
144 # The up and down scancodes are the scancodes of the keys to simulate when
145 # the wheel is turned up and down, respectively.
146 #
147 # Scancodes can be specified numerically or symbolically; the symbolic names
148 # are determined by the keymap (see below), if no keymap is loaded the default
149 # is the standard US QWERTY keyboard with the following names available:
150 # letters (a-z), numbers (zero-nine), function keys (F1-F12), the keypad
151 # numbers (KP_0-KP_9) and other keys (KP_Multiply, KP_Subtract, KP_Add,
152 # KP_Period, KP_Enter, and KP_Divide), and the following - minus, equal,
153 # Delete, Tab, bracketleft, bracketright, Return, Control, semicolon,
154 # apostrophe, grave, Shift, backslash, comma, period, slash, Shift, Alt, space,
155 # Caps_Lock, Num_Lock, Scroll_Lock, Last_Console, less, Control_backslash,
156 # AltGr, Break, Find, Up, Prior, Left, Right, Select, Down, Next, Insert,
157 # and Remove.
158 #
159 # Note that this option has no effect unless the IntelliMouse or IMPS2 mouse
160 # type is used (see above). Also note that the simulated keypresses are
161 # instantaneous, so they cannot be used for functions that require a key to
162 # be held down for a certain length of time.
163 
164 #  This example simulates a press of the left bracket ([) when the wheel is
165 #  turned up and a press of the right bracket (]) when the wheel is turned
166 #  down (good for selecting items in Quake II):
167 # mouse_fake_kbd_event bracketleft bracketright
168 
169 # Keyboard config:
170 
171 # kbd_keymap allows you to use an alternate keyboard layout with programs that
172 # use raw keyboard support by translating scancodes from the desired layout to
173 # their equivalents in the layout expected by the program. This option has no
174 # affect on programs that do not use raw keyboard.
175 #
176 # Keymap files to convert between any two arbitrary keyboard layouts can be
177 # generated with the svgakeymap utility, but there are limitations to the
178 # translations that can be performed. Read the file README.keymap in the
179 # svgalib documentation directory for more in-depth information.
180 #
181 # You must specify the full path to the keymap file; it is recommended that
182 # keymaps be kept in the same directory as libvga.config, normally /etc/vga.
183 # The keymap specified in the configuration file can be overriden by setting
184 # the environment variable SVGALIB_KEYMAP to point to another keymap file;
185 # this can be useful for setting keymaps on a per-program basis.
186 #
187 #  This example will use the provided US-Dvorak to US-QWERTY map to allow a
188 #  Dvorak keyboard layout to be used with a program that expects a standard US
189 #  QWERTY keyboard, for instance Quake:
190 # kbd_keymap /etc/vga/dvorak-us.keymap
191 
192 # There is a potential security risk in allowing users to remap keyboard
193 # scancodes at will; with this option enabled only keymap files owned by
194 # root can be used. Normally you should leave this on, but if you have a
195 # single-user box or you really trust your users you may find it convenient
196 # to run without it and allow users to load arbitrary keymaps.
197 
198 kbd_only_root_keymaps
199 
200 # kbd_fake_mouse_event, as it says, sends a fake mouse event to the program.
201 # The format is: kbd_fake_mouse_event scancode [flag(s)] command [argument]
202 #   Scancode is a raw scancode or a descriptive name, the same as with fake
203 #   keyboard events (see above). If you use keymap conversion, specify
204 #   scancodes for the keyboard layout the program will receive.
205 #   Flags:   down   - trigger event when the key is pressed (default)
206 #      up     - the opposite
207 #      both   - trigger in both case, if pressed/released
208 #      repeat - repeat events if the key is kept pressed (off by default)
209 #   commands: delta[xyz]  - send a fake delta event as if you have moved your
210 #        mouse. If the parameter is 'off' / 'on' it will turn
211 #       off/on the respective mouse axis (requires a
212 #       parameter, of course)
213 #       button[123] - send a fake event that the mouse button is pressed
214 #       or released that's given by the parameter.
215 #        ('pressed' or 'released')
216 # Here are some examples:
217 
218 #  This is one I use in *uake: it turns around, looks down a bit and when the
219 #  key is released it does the opposite, so it gets back to the starting state.
220 #  With this one and the help of a rocket you can fly though the whole map :)
221 #  (Scancode 28 is enter)
222 # kbd_fake_mouse_event 28 both deltax 8182 down deltay -1500 up deltay 1500
223 
224 #  This one will switch off the y axis of the mouse while the key (right ctrl)
225 #  is kept pressed.
226 # kbd_fake_mouse_event 97 down deltay off  up deltay on
227 
228 #  This one is the same as if you were pressing the left mouse button. (But
229 #  if you move your mouse then the button state will reset even if you keep
230 #  right ctrl down...)
231 # kbd_fake_mouse_event 97 down button1 pressed up button1 released
232 
233 # Monitor type:
234 
235 # Only one range can be specified for the moment.  Format:
236 # HorizSync min_kHz max_kHz
237 # VertRefresh min_Hz max_Hz
238 
239 # Typical Horizontal sync ranges
240 # (Consult your monitor manual for Vertical sync ranges)
241 #
242 # 31.5 - 31.5 kHz (Standard VGA monitor, 640x480 @ 60 Hz)
243 # 31.5 - 35.1 kHz (Old SVGA monitor, 800x600 @ 56 Hz)
244 # 31.5 - 35.5 kHz (Low-end SVGA, 8514, 1024x768 @ 43 Hz interlaced)
245 # 31.5 - 37.9 kHz (SVGA monitor, 800x600 @ 60 Hz, 640x480 @ 72 Hz)
246 # 31.5 - 48.3 kHz (SVGA non-interlaced, 800x600 @ 72 Hz, 1024x768 @ 60 Hz)
247 # 31.5 - 56.0 kHz (high frequency, 1024x768 @ 70 Hz)
248 # 31.5 - ???? kHz (1024x768 @ 72 Hz)
249 # 31.5 - 64.3 kHz (1280x1024 @ 60 Hz)
250 
251 HorizSync 31.5 35.5
252 VertRefresh 50 90
253 
254 # If you have a NeoMagic card on a Toshiba Libretto 100, 110 use that instead
255 
256 # HorizSync 31.5 70
257 # VertRefresh 50 100
258 # Modeline "800x480"   50  800  856  976 1024   480  483  490  504 +hsync +vsync
259 # newmode  800 480 256       800 1
260 # newmode  800 480 32768    1600 2
261 # newmode  800 480 65536    1600 2
262 # newmode  800 480 16777216 2400 3
263 
264 # Monitor timings
265 #
266 # These are prefered over the default timings (if monitor and chipset
267 # can handle them). Not all drivers use them at the moment, and Mach32
268 # has its own syntax (see below).
269 # The format is identical to the one used by XFree86, but the label
270 # following the modeline keyword is ignored by svgalib.
271 #
272 # Here some examples:
273 
274 # modeline "640x480@100"  43  640  664  780  848   480  483  490  504
275 # modeline "800x600@73"   50  800  856  976 1024   600  637  643  666
276 # modeline "1024x768@75"  85 1024 1048 1376 1400   768  771  780  806
277 
278 # It seems there is a need for a 512x384 mode, this timing was donated
279 # by Simon Hosie <gumboot@clear.net.nz>: (it is 39kHz horz by 79Hz vert)
280 
281 # Modeline "512x384@79"     25.175 512  522  598  646   384  428  436  494
282 
283 # Here's a 400x300 Modeline (created by svidtune). Note that for
284 # doublescan modes, the Vertical values are half the real one (so XFree86
285 # modelines can be used). 
286 
287 # Modeline "400x300@72" 25.000 400 440 504 520 300 319 322 333 doublescan
288 
289 # Here is a mode for a ZX Spectrum emulator:
290 # Modeline "256x192@73" 12.588 256 269 312 360 192 208 212 240 doublescan
291 # newmode 256 192 256 256 1
292 
293 # the width must be divisible by 8. Some cards require even divisiblity by
294 # 16, so that's preferable, since there are no standard modes where the
295 # width is not divisible by 16.
296 
297 # The following modes are defined in svgalib, but have no timings in
298 # timing.c, so you'll have to add a modeline in order to use them:
299 # 1280x720, 1360x768, 1800x1012, 1920x1080, 1920x1440, 2048x1152
300 # and 2048x1536   
301 
302 # Mach32 timings:
303 
304 # e.g. Setup a 320x200 mode for the mach32:
305 
306 #define 320x200x32K 320x200x64K 320x200x16M 320x200x16M32
307 #                     16 320 392 464 552 200 245 265 310
308 
309 # These are REQUIRED for above mode, please edit to suit your monitor.
310 # (No, I won't pay for a new one)
311 # HorizSync 29 65
312 # VertRefresh 42 93.5
313 
314 # Chipset type:
315 #
316 # Use one of the following force chipset type.
317 # Autodetects if no chipset is specified.
318 #
319 # If you have a PCI or AGP card, don't use chipset type forcing.
320 # If the card is not autodetected, its a bug, and it will probably
321 # not work even with forcing. Try running vgatest (with no chipset 
322 # line), and send to me (matan@svgalib.org) the output, a copy of
323 # /proc/pci (or lspci -n -vv) and whatever info you have on the card. 
324 #
325 # If a chipset driver gives trouble, try forcing VGA.
326 
327 # chipset VGA  # Standard VGA
328 # chipset EGA  # EGA
329 # chipset ET3000 # Tseng ET3000
330 # chipset ET4000 # Tseng ET4000
331 # chipset Cirrus # Cirrus Logic GD542x
332 # chipset TVGA  # Trident TVGA8900/9000
333 # chipset Oak  # Oak Technologies 037/067/077
334 # chipset S3  # S3 chipsets
335 # chipset GVGA6400 # Genoa 6400
336 # chipset ARK  # ARK Logic
337 # chipset ATI  # old ATI VGA
338 # chipset Mach32 # ATI Mach32
339 # chipset ALI  # ALI2301
340 # chipset Mach64 # ATI Mach64 - deprecated
341 # chipset ET6000        # Tseng ET6000
342 # chipset APM   # Alliance Technology AT 24/25/3D
343 # chipset NV3  # nVidia Riva 128 / TNT / GeForce
344  chipset VESA          # nicely behaved Vesa Bioses
345 # chipset MX  # MX86251 (some Voodoo Rush boards)
346 # chipset PARADISE  # WD90C31
347 # chipset RAGE  # RagePro (and might work with some older mach64)
348 # chipset BANSHEE # Banshee/V3.
349 # chipset SIS  # SiS 5597/6326/620/530 cards / integrated vga.
350 # chipset I740  # Intel i740 based cards.
351 # chipset NEOMAGIC
352 # chipset LAGUNA # Cirrus Logic Laguna series (546X)
353 # chipset FBDEV  # Use kernel fbdev, instead of direct hardware.
354 # chipset G400  # Matrox Mystique/G100/G200/G400/G450
355 # chipset R128  # Ati Rage128
356 # chipset SAVAGE # S3 chipsets Savage
357 # chipset C&T  # Chips and Technologies
358 
359 # EGA Color/mono mode:
360 # Required if chipset is EGA.
361 #
362 # Use one of the following digits to force color/mono:
363 
364 # monotext  # Card is in monochrome emulation mode
365 # colortext # Card is in color emulation mode
366 colortext
367 
368 # RAMDAC support:
369 # Some chipsets (e.g. S3 and ARK) allows specifying a RAMDAC type.
370 # If your RAMDAC is not autodetected, you can try specifying it.
371 # Do NOT specify a RAMDAC if you card uses the S3 Trio chipset
372 # (the RAMDAC is built in).
373 
374 # Ramdac Sierra32K
375 # Ramdac SC15025
376 # Ramdac SDAC         # S3 SDAC
377 # Ramdac GenDAC       # S3 GenDAC
378 # Ramdac ATT20C490    # AT&T 20C490, 491, 492 (and compatibles)
379 # Ramdac ATT20C498    # AT&T 20C498
380 # Ramdac IBMRGB52x    # IBM RGB524, 526, 528 (and compatibles)
381 
382 # Dotclocks:
383 # Some chipsets needs a list of dot clocks for optimum operation.  Some
384 # includes or supports a programmable clock chip.  You'll need to specify
385 # them here.
386 
387 # Fixed clocks example:
388 # (The following is just an example, get the values for your card from
389 #  you XF86Config)
390 
391 # Clocks 25.175 28.3 40 70 50 75 36 44.9 0 118 77 31.5 110 65 72 93.5
392 
393 # Programmable clockchip example:
394 
395 # Clockchip ICD2061A  # The only one supported right now
396 
397 
398 
399 # Here are miscellaneous options to help with specific problems.
400 
401 # VesaText       # Helps the VESA driver with text mode restoration
402         # problems.
403 
404 # VesaSave 14       # changing value might help text mode restoring
405         # problems with VESA driver. Legal values: 0-15
406 
407 # NoVCControl       # Disables svgalib's finding a new VC if run
408         # from X. Good fo using dumpreg under X, but
409         # probably bad for standard usage.
410 
411 
412 # RageDoubleClock     # If your card is based on ATI's rage card, and
413         # the pixel clock is double what it should be 
414         # (main symptom is some modes are out of sync),
415         # try enabling this. If it helps, please report to
416         # me (matan@svgalib.org) 
417 
418 # NeoMagicLibretto100
419         # Enable if you have a NeoMagic card on a Toshiba
420         # Libretto 100, 110, etc

344  chipset VESA          # nicely behaved Vesa Bioses

uncomment L344, 使用 VESA driver。基本上以目前的顯示卡來說, vesa driver 應該都可以正確執行。都不改也可以, svgalib 會自動偵測可用的顯示模式。


再來看看測試目前可以設定的解析度有哪些。

svgalib-1.9.25/demos/vgatest (這是 svgalib 自帶 source code 裡頭的程式)

執行畫面:

vga driver
寄件者 svgalib test

使用的是 vga driver, 最近研究了 vga, 看這上面列出的解析度, 我已經了解為什麼是這些解析度, 在我剛開始使用 svgalib 時, 我完全不知道 vga 相關技術。

vesa driver

在我的卡上, vga driver 只有 10 種解析度可使用, vesa driver 就有比較多的解析度可用。vesa 這部份不知道為什麼可以有這麼多的解析度可以選擇, 列入下次的功課。

640X480X24 bit color



我選擇了幾個模式, 似乎 640x480X24 bit color 是比較好的選擇, 有些模式跑不起來或是怪怪的。選擇了要使用的解析度之後, 就可以來練習寫 console 繪圖程式了。理論上 vesa driver 應該可以跑得起來, 若不行, 那只能使用 vga driver, 不過只能有 320x200x256 色這樣的環境。如果連 vgatest 都跑不了, 那還是用 linux framebuffer 吧!

simple os 目前只能使用 320x200x256 色, 我希望可以編寫 vesa 的版本, 使其使用 640x480X24 bit color, 不過資料真的不好找。

1.4.3 的版本需要使用 root 權限來執行 compile 出來的程式, 1.9.25 則需要 kernel module, 其實不好用, 因為已經跟不上 linux kernel 的版本了。練習的話建議使用 1.4.3 即可。

現在我有了 simple os, 比較少碰 svgalib 了, 紀錄下來做個回憶。

在我的 mac mini/eeepc 901 intel i915 card 上, 若是載入 i915 gpu module, vgatest 沒有任何一個顯示模式可正常使用, 大概是相衝了。我將整個 i915 module 移除則可以正常顯示其中幾個模式。

fbset -s 可以用來測 linux framebuffer 是不是起動了。以下是有啟動 linux framebuffer 的結果。

mode "640x480-60"
    # D: 25.176 MHz, H: 31.469 kHz, V: 59.942 Hz
    geometry 640 480 640 480 4
    timings 39721 48 16 33 10 96 2
    rgba 6/0,6/0,6/0,0/0
endmode

為了學習 svgalib 我還特地買了這本書:
linux graphics programming with svgalib
寄件者 computer books

也可參考這篇 - SVGAlib Tutorials

svgalib 有兩個系列的繪圖函式:
vga_xxx
gl_xxx

gl_xxx 系列在繪圖上比較快, 用法有點複雜, 建議從 vga_xxx 開始學習比較簡單。

svgalib 很低階, 有些用法需要知道 vga 相關技術, 否則看 man page 的說明, 無法知道為什麼會有這樣的限制。

為什麼要學習繪圖程式, 因為我想輸出中文, 使用中文的人如果不會寫中文系統, 好像說不過去, 無論如何我想要知道如何秀出中文, 若不依靠中文系統或是中文終端機要如何秀出中文。早期 dos 時代有很多這樣的資料, 現在的書似乎比較少著墨。

我有一本書專門在講這個:
文字中文系統徹底研究 - 輸入法與秀字

有點過時, 不過基本原理是不變的。

寫這篇時, 我竟然手癢, 順手修改了 jmcce, 讓他可以在某種程度上繼續執行。



ref:
http://www.svgalib.org/
http://mail.arava.co.il/~matan/svgalib/
https://github.com/ryanmcgrath/svgalib-1

git 版本讓我驚訝, 原來新版的 svgalib source code 早已經搬到 github 上了。

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。