initial commit
This commit is contained in:
+296
@@ -0,0 +1,296 @@
|
||||
package pdf
|
||||
|
||||
// The most colorants a colour value can have. /DeviceN is capped at 32
|
||||
// colorants by the format (ISO 32000-2, 8.6.6.5), which is the widest case.
|
||||
MAX_COLOR_COMPONENTS :: 32
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stroking
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Line_Cap :: enum u8 {
|
||||
Butt = 0,
|
||||
Round = 1,
|
||||
Square = 2,
|
||||
}
|
||||
|
||||
Line_Join :: enum u8 {
|
||||
Miter = 0,
|
||||
Round = 1,
|
||||
Bevel = 2,
|
||||
}
|
||||
|
||||
// MAX_DASH_ENTRIES bounds a /D array from the file. The format sets no limit;
|
||||
// eight is the most any real design uses and the array is copied by value into
|
||||
// every saved graphics state.
|
||||
MAX_DASH_ENTRIES :: 16
|
||||
|
||||
Stroke_State :: struct {
|
||||
line_width: f64,
|
||||
line_cap: Line_Cap,
|
||||
line_join: Line_Join,
|
||||
miter_limit: f64,
|
||||
// Dash pattern in user space, with `dash_count` valid entries. An entry of
|
||||
// zero is legal and means a dot; an all-zero array means "no dashing",
|
||||
// because a pattern that never advances would loop forever.
|
||||
dash: [MAX_DASH_ENTRIES]f64,
|
||||
dash_count: int,
|
||||
dash_phase: f64,
|
||||
}
|
||||
|
||||
STROKE_STATE_DEFAULT :: Stroke_State{
|
||||
line_width = 1,
|
||||
line_cap = .Butt,
|
||||
line_join = .Miter,
|
||||
miter_limit = 10,
|
||||
}
|
||||
|
||||
// stroke_has_dash reports whether the pattern actually breaks the line. An
|
||||
// array of all zeros, or one whose entries sum to zero, must be treated as
|
||||
// solid or the dasher never advances (ISO 32000-2, 8.4.3.6).
|
||||
stroke_has_dash :: proc(s: ^Stroke_State) -> bool {
|
||||
if s == nil || s.dash_count <= 0 { return false }
|
||||
total := 0.0
|
||||
for i in 0 ..< min(s.dash_count, MAX_DASH_ENTRIES) {
|
||||
if s.dash[i] < 0 { return false }
|
||||
total += s.dash[i]
|
||||
}
|
||||
return total > 0
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Transparency
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Blend_Mode is the set from ISO 32000-2, 11.3.5. `Compatible` is a legacy
|
||||
// spelling of Normal and behaves identically.
|
||||
Blend_Mode :: enum u8 {
|
||||
Normal = 0,
|
||||
Multiply,
|
||||
Screen,
|
||||
Overlay,
|
||||
Darken,
|
||||
Lighten,
|
||||
Color_Dodge,
|
||||
Color_Burn,
|
||||
Hard_Light,
|
||||
Soft_Light,
|
||||
Difference,
|
||||
Exclusion,
|
||||
// The four non-separable modes operate on all three RGB components at once.
|
||||
Hue,
|
||||
Saturation,
|
||||
Color,
|
||||
Luminosity,
|
||||
}
|
||||
|
||||
// blend_mode_from_name maps a /BM name. An unrecognised name is Normal, which
|
||||
// the format requires (11.3.5.2) rather than treating as an error.
|
||||
blend_mode_from_name :: proc(name: string) -> Blend_Mode {
|
||||
switch name {
|
||||
case "Normal", "Compatible": return .Normal
|
||||
case "Multiply": return .Multiply
|
||||
case "Screen": return .Screen
|
||||
case "Overlay": return .Overlay
|
||||
case "Darken": return .Darken
|
||||
case "Lighten": return .Lighten
|
||||
case "ColorDodge": return .Color_Dodge
|
||||
case "ColorBurn": return .Color_Burn
|
||||
case "HardLight": return .Hard_Light
|
||||
case "SoftLight": return .Soft_Light
|
||||
case "Difference": return .Difference
|
||||
case "Exclusion": return .Exclusion
|
||||
case "Hue": return .Hue
|
||||
case "Saturation": return .Saturation
|
||||
case "Color": return .Color
|
||||
case "Luminosity": return .Luminosity
|
||||
}
|
||||
return .Normal
|
||||
}
|
||||
|
||||
blend_mode_is_separable :: proc(m: Blend_Mode) -> bool {
|
||||
return m < .Hue
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Colour
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Color is a colour value in some colour space: `n` components, each in the
|
||||
// range that space defines (which is not always 0..1 -- Lab and Indexed both
|
||||
// use other ranges).
|
||||
Color :: struct {
|
||||
n: int,
|
||||
v: [MAX_COLOR_COMPONENTS]f64,
|
||||
}
|
||||
|
||||
color_make :: proc(values: ..f64) -> (c: Color) {
|
||||
c.n = min(len(values), MAX_COLOR_COMPONENTS)
|
||||
for i in 0 ..< c.n { c.v[i] = values[i] }
|
||||
return
|
||||
}
|
||||
|
||||
// Paint_Source is one half of the graphics state's colour: what a fill (or a
|
||||
// stroke) paints with. A pattern takes precedence over the colour components,
|
||||
// which for an uncoloured tiling pattern still carry the underlying colour.
|
||||
Paint_Source :: struct {
|
||||
cs: ^Colorspace,
|
||||
color: Color,
|
||||
pattern: ^Pattern,
|
||||
}
|
||||
|
||||
// Paint is the fully-resolved instruction handed to a Device for one drawing
|
||||
// operation: everything it needs and nothing it has to look up.
|
||||
Paint :: struct {
|
||||
cs: ^Colorspace,
|
||||
color: Color,
|
||||
// The colour converted to RGB once, so a device that only wants RGB need
|
||||
// not carry a colour-management path.
|
||||
rgb: [3]f64,
|
||||
pattern: ^Pattern,
|
||||
alpha: f64,
|
||||
blend: Blend_Mode,
|
||||
soft_mask: ^Soft_Mask,
|
||||
// Set when the operation paints a shape rather than a colour: image masks
|
||||
// and text clipping. Devices that ignore colour can skip the conversion.
|
||||
is_mask: bool,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Text state (ISO 32000-2, 9.3)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Text_Render_Mode :: enum u8 {
|
||||
Fill = 0,
|
||||
Stroke = 1,
|
||||
Fill_Stroke = 2,
|
||||
Invisible = 3,
|
||||
Fill_Clip = 4,
|
||||
Stroke_Clip = 5,
|
||||
Fill_Stroke_Clip = 6,
|
||||
Clip = 7,
|
||||
}
|
||||
|
||||
text_render_mode_fills :: proc(m: Text_Render_Mode) -> bool {
|
||||
return m == .Fill || m == .Fill_Stroke || m == .Fill_Clip || m == .Fill_Stroke_Clip
|
||||
}
|
||||
|
||||
text_render_mode_strokes :: proc(m: Text_Render_Mode) -> bool {
|
||||
return m == .Stroke || m == .Fill_Stroke || m == .Stroke_Clip || m == .Fill_Stroke_Clip
|
||||
}
|
||||
|
||||
text_render_mode_clips :: proc(m: Text_Render_Mode) -> bool {
|
||||
return m >= .Fill_Clip
|
||||
}
|
||||
|
||||
Text_State :: struct {
|
||||
font: ^Font,
|
||||
size: f64,
|
||||
char_spacing: f64, // Tc, unscaled text-space units
|
||||
word_spacing: f64, // Tw, applied only to single-byte code 32
|
||||
// Th: the horizontal scaling operator gives a percentage; this holds the
|
||||
// fraction, so the default is 1, not 100.
|
||||
horiz_scale: f64,
|
||||
leading: f64, // TL
|
||||
rise: f64, // Ts
|
||||
render_mode: Text_Render_Mode,
|
||||
knockout: bool,
|
||||
}
|
||||
|
||||
TEXT_STATE_DEFAULT :: Text_State{
|
||||
size = 0,
|
||||
horiz_scale = 1,
|
||||
knockout = true,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Graphics state (ISO 32000-2, 8.4)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Graphics_State :: struct {
|
||||
ctm: Matrix,
|
||||
|
||||
fill: Paint_Source,
|
||||
stroke: Paint_Source,
|
||||
fill_alpha: f64,
|
||||
stroke_alpha: f64,
|
||||
|
||||
stroke_state: Stroke_State,
|
||||
|
||||
blend: Blend_Mode,
|
||||
soft_mask: ^Soft_Mask,
|
||||
|
||||
text: Text_State,
|
||||
|
||||
// Depth of the device clip stack at the time this state was saved, so `Q`
|
||||
// can pop exactly the clips that `q` was holding.
|
||||
clip_depth: int,
|
||||
|
||||
// Parsed but not honoured by the software renderer; kept so a display-list
|
||||
// consumer and any future colour-managed path can see them.
|
||||
render_intent: string,
|
||||
flatness: f64,
|
||||
smoothness: f64,
|
||||
stroke_adjust: bool,
|
||||
alpha_is_shape: bool,
|
||||
black_point_compensation: bool,
|
||||
overprint_fill: bool,
|
||||
overprint_stroke: bool,
|
||||
overprint_mode: int,
|
||||
}
|
||||
|
||||
// gstate_default is the initial state at the start of a page or form, before
|
||||
// any operator runs (8.4.1). Colour spaces are filled in by the interpreter,
|
||||
// which needs the Document to resolve DeviceGray.
|
||||
gstate_default :: proc(ctm: Matrix) -> Graphics_State {
|
||||
return Graphics_State{
|
||||
ctm = ctm,
|
||||
fill_alpha = 1,
|
||||
stroke_alpha = 1,
|
||||
stroke_state = STROKE_STATE_DEFAULT,
|
||||
blend = .Normal,
|
||||
text = TEXT_STATE_DEFAULT,
|
||||
render_intent = "RelativeColorimetric",
|
||||
flatness = 1,
|
||||
smoothness = 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Gstate_Stack is the `q`/`Q` stack. It is depth-capped because a content
|
||||
// stream can push without ever popping, and each entry is a fat value.
|
||||
MAX_GSTATE_DEPTH :: 64
|
||||
|
||||
Gstate_Stack :: struct {
|
||||
items: [dynamic]Graphics_State,
|
||||
}
|
||||
|
||||
gstate_stack_make :: proc(allocator := context.allocator) -> Gstate_Stack {
|
||||
return Gstate_Stack{items = make([dynamic]Graphics_State, 0, 16, allocator)}
|
||||
}
|
||||
|
||||
gstate_stack_destroy :: proc(s: ^Gstate_Stack) {
|
||||
if s == nil { return }
|
||||
delete(s.items)
|
||||
s^ = {}
|
||||
}
|
||||
|
||||
// gstate_push saves a copy of `gs`. Returns false when the stack is full, in
|
||||
// which case the caller must not pop for this `q` -- otherwise the stack
|
||||
// underflows on the matching `Q` and the rest of the page draws in the wrong
|
||||
// state.
|
||||
gstate_push :: proc(s: ^Gstate_Stack, gs: Graphics_State) -> bool {
|
||||
if len(s.items) >= MAX_GSTATE_DEPTH { return false }
|
||||
append(&s.items, gs)
|
||||
return true
|
||||
}
|
||||
|
||||
gstate_pop :: proc(s: ^Gstate_Stack) -> (gs: Graphics_State, ok: bool) {
|
||||
if len(s.items) == 0 { return {}, false }
|
||||
gs = s.items[len(s.items) - 1]
|
||||
pop(&s.items)
|
||||
return gs, true
|
||||
}
|
||||
|
||||
gstate_depth :: proc(s: ^Gstate_Stack) -> int {
|
||||
return len(s.items)
|
||||
}
|
||||
Reference in New Issue
Block a user