File: text-userfont2.lua

package info (click to toggle)
oocairo 1.4-1.2
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 712 kB
  • sloc: ansic: 3,352; makefile: 59; sh: 15
file content (143 lines) | stat: -rw-r--r-- 5,892 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
-- This is another example of what can be done with user fonts, but this
-- time with glyph images and extents taken from real fonts but rendered
-- with distortion.
--
-- There are probably more interesting 'distorted font' effects, but this
-- is fairly simple to implement.  The normal glyph shape is simply cut
-- by some randomly placed thin lines passing through it at different
-- angles.

local Cairo = require "oocairo"

if not Cairo.user_font_face_create then
    error("User font example requires module built with Cairo 1.8", 0)
end

local FONT_SIZE = 70
local PI = math.asin(1) * 2

local function make_chopped_font (basefont)
    local function render_glyph (font, glyph_index, cr, extents)
        -- Use a temporary context to select the base font and copy the
        -- appropriate glyph extents into the 'extents' argument.  That is,
        -- the user font glyph extents should be the same as those of
        -- the base font.
        local glyphcr = Cairo.context_create(cr:get_target())
        glyphcr:set_scaled_font(basefont)
        cr:scale(1/FONT_SIZE, 1/FONT_SIZE)
        local glyph_tbl = { { glyph_index, 0, 0 } }
        local glyph_extents = glyphcr:glyph_extents(glyph_tbl)
        for k, v in pairs(glyph_extents) do extents[k] = v / FONT_SIZE end

        -- Construct a temporary image surface to draw the glyph on before
        -- it's copied to the 'cr' we were provided with by Cairo.  This
        -- could probably be avoided somehow, and certainly isn't ideal
        -- from an efficiency point of view.  Note that we're using the 'a8'
        -- format, since the glyph image is greyscale, so we don't need colour.
        local wd, ht = cr:user_to_device_distance(glyph_extents.width + 1,
                                                  glyph_extents.height + 1)
        local tmpsurface = Cairo.image_surface_create("a8", wd, ht)
        local tmpcr = Cairo.context_create(tmpsurface)

        -- Fill in the glyph shape by copying its path from the context with
        -- the base font selected and drawing it onto the temporary surface.
        glyphcr:glyph_path(glyph_tbl)
        tmpcr:new_path()
        tmpcr:translate(-glyph_extents.x_bearing, -glyph_extents.y_bearing)
        tmpcr:append_path(glyphcr:copy_path())
        tmpcr:set_source_rgba(0, 0, 0, 1)
        tmpcr:fill()

        -- Construct a path for a bunch of random lines going across the
        -- glyph shape.
        tmpcr:identity_matrix()
        local cx, cy = wd / 2, ht / 2
        local radius = cx * cx + cy * cy
        for _ = 1, 15 do
            local ang = math.random() * 2 * PI
            local xd, yd = radius * math.cos(ang), radius * math.sin(ang)
            local xoffset = math.random() * cx - cx / 2
            local yoffset = math.random() * cy - cy / 2
            tmpcr:move_to(cx + -xd + xoffset, cy + -yd + yoffset)
            tmpcr:line_to(cx + xd + xoffset,  cy + yd + yoffset)
        end

        -- Select a 'hair line' width, that is a line which is one pixel
        -- wide in the output, whatever the font scaling is.  Actually, this
        -- will just use the horizontal distance covered by a pixel, so
        -- hopefully the pixels are close to being square.
        tmpcr:set_line_width(cr:device_to_user_distance(1, 1))

        -- Remove the ink from the glyph shape wherever the lines pass, leaving
        -- transparent gaps.
        tmpcr:set_source_rgba(0, 0, 0, .8)
        tmpcr:set_operator("dest-out")  -- remove ink rather than adding it
        tmpcr:stroke()

        -- Use the distorted glyph shape we've constructed to ink in the
        -- appropriate parts of the output, on the context which Cairo is
        -- using to create the glyph shape to cache.
        cr:mask(tmpsurface, glyph_extents.x_bearing, glyph_extents.y_bearing)
    end

    local function text_to_glyphs (font, text, want_clusters)
        -- This just passes the request on to the base font, although we
        -- have to scale the positions back down to font units, because
        -- they'll be scaled up to the font size of the base font.
        local g = basefont:text_to_glyphs(0, 0, text)
        for i, t in ipairs(g) do
            t[2] = t[2] / FONT_SIZE
            t[3] = t[3] / FONT_SIZE
        end
        return g
    end

    return Cairo.user_font_face_create({
        render_glyph = render_glyph,
        text_to_glyphs = text_to_glyphs,
    })
end

local surface = Cairo.image_surface_create("rgb24", 525, 300)
local cr = Cairo.context_create(surface)

-- White background.
cr:set_source_rgb(1, 1, 1)
cr:paint()

local x, y = 15, 5 + FONT_SIZE

-- Draw lines of text, each in a different colour and font.
for _, info in ipairs{
    { "Quick brown", { .3, 0, 0 },
      { "sans", "normal", "bold" } },
    { "fox jumped…", { 0, .3, 0 },
      { "serif", "italic" } },
    -- The last row won't show up unless you have a font with cuneiform
    -- characters in.  They're just random characters, not meaningful text,
    -- but a bit more interesting to look at than boring old Latin letters.
    { "𒀽𒍬𒍅𒌤", { 0, 0, .3 },
      { "akkadian" } },
} do
    -- Create a normal Cairo 'toy' font to use as the base.  The glyphs from
    -- this font will be used with our custom rendering to distort them.
    local basefont_face = Cairo.toy_font_face_create(unpack(info[3]))
    local font_mat = Cairo.matrix_create()
    font_mat:scale(FONT_SIZE, FONT_SIZE)
    local ctm = Cairo.matrix_create()
    local basefont = Cairo.scaled_font_create(basefont_face, font_mat, ctm)

    cr:set_font_face(make_chopped_font(basefont))
    cr:set_font_size(FONT_SIZE)

    local line, color = unpack(info)
    cr:move_to(x, y)
    cr:set_source_rgb(unpack(color))
    cr:show_text(line)

    y = y + basefont:extents().height * 1.2
end

surface:write_to_png("text-userfont2.png")

-- vi:ts=4 sw=4 expandtab