Ctrl K
ComunidadGuías…

koreader-plugin/bookorbit.koplugin/bookorbit_board_metrics.lua

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
--[[--
Shared geometry for the widget dashboard.

Every size on the board is given in density-independent units (dp) and
converted here, so the board scales like the rest of KOReader
(Screen:scaleBySize, short side / 600) plus one density step: a 10" panel
shows more content instead of the 6" page blown up, and a phone gets
finger-sized type. The reader's interface-size preference is applied after
that device adjustment. Font sizes go through the same scale, so text and
boxes stay in proportion.

Line heights mirror the widgets that draw them: a single-line TextWidget is
ceil(face height) tall, where Noto's face height is 1.362 em, and a
TextBoxWidget line is round(1.3 em).
]]

local Blitbuffer = require("ffi/blitbuffer")
local Device = require("device")
local Font = require("ui/font")

local Screen = Device.screen

local BoardMetrics = {}

BoardMetrics.UI_SCALES = { 1, 1.05, 1.1, 1.15, 1.2 }
BoardMetrics.DEFAULT_UI_SCALE = 1.05

local FACE_HEIGHT_EM = 1.362
local FACES = {
    sans = "NotoSans-Regular.ttf",
    sans_italic = "NotoSans-Italic.ttf",
    serif = "NotoSerif-Regular.ttf",
    serif_italic = "NotoSerif-Italic.ttf",
}

-- The sixteen gray levels e-ink panels can show, by name.
BoardMetrics.INK = {
    black = Blitbuffer.COLOR_BLACK,
    g1 = Blitbuffer.COLOR_GRAY_1,
    g2 = Blitbuffer.COLOR_GRAY_2,
    g3 = Blitbuffer.COLOR_GRAY_3,
    g4 = Blitbuffer.COLOR_GRAY_4,
    g5 = Blitbuffer.COLOR_GRAY_5,
    g6 = Blitbuffer.COLOR_GRAY_6,
    g7 = Blitbuffer.COLOR_GRAY_7,
    g8 = Blitbuffer.COLOR_DARK_GRAY,
    g9 = Blitbuffer.COLOR_GRAY_9,
    ga = Blitbuffer.COLOR_GRAY,
    gb = Blitbuffer.COLOR_GRAY_B,
    gc = Blitbuffer.COLOR_LIGHT_GRAY,
    gd = Blitbuffer.COLOR_GRAY_D,
    ge = Blitbuffer.COLOR_GRAY_E,
    white = Blitbuffer.COLOR_WHITE,
}

-- Hex values of the same grays, for the SVG icons drawn in them.
BoardMetrics.INK_HEX = {
    black = "#000000", g1 = "#111111", g2 = "#222222", g3 = "#333333", g4 = "#444444",
    g5 = "#555555", g6 = "#666666", g7 = "#777777", g8 = "#888888", g9 = "#999999",
    ga = "#aaaaaa", gb = "#bbbbbb", gc = "#cccccc", gd = "#dddddd", ge = "#eeeeee", white = "#ffffff",
}

local cache = nil
local user_scale = BoardMetrics.DEFAULT_UI_SCALE

local function clamp(v, lo, hi)
    if v < lo then return lo end
    if v > hi then return hi end
    return v
end

function BoardMetrics.normalizeUserScale(value)
    local numeric = tonumber(value)
    if not numeric then return BoardMetrics.DEFAULT_UI_SCALE end
    local rounded = math.floor(numeric * 100 + 0.5) / 100
    if rounded == 1.25 then return 1.2 end
    for _, scale in ipairs(BoardMetrics.UI_SCALES) do
        if rounded == scale then return scale end
    end
    return BoardMetrics.DEFAULT_UI_SCALE
end

function BoardMetrics.setUserScale(value)
    user_scale = BoardMetrics.normalizeUserScale(value)
    cache = nil
    return user_scale
end

-- The density step: sqrt of how much shorter or longer the panel's short side
-- is than a 6" one (3.6"), kept gentle and within [0.78, 1.12].
function BoardMetrics.densityAdjust(short_px, dpi)
    if not short_px or not dpi or dpi <= 0 then return 1 end
    local inches = short_px / dpi
    local adj = clamp(math.sqrt(3.6 / inches), 0.78, 1.12)
    return math.floor(adj * 100 + 0.5) / 100
end

-- Recomputed when the screen geometry changes (rotation, DPI override).
function BoardMetrics.refresh()
    local w, h = Screen:getWidth(), Screen:getHeight()
    local short = math.min(w, h)
    local adj = BoardMetrics.densityAdjust(short, Screen:getDPI())
    -- Screen:scaleBySize's own scale: the short side over 600, averaged with
    -- DPI / 160 under a user DPI override. Computed here rather than read
    -- back from scaleBySize(1e6), which rounds up at the sixth decimal and
    -- turns an exact 600 dp screen into 599.9994.
    local size_scale = short / 600
    local dpi_scale = Screen.dpi_override and (Screen:getDPI() / 160) or size_scale
    local base = (size_scale + dpi_scale) / 2
    cache = {
        w = w,
        h = h,
        adj = adj,
        scale = user_scale,
        dp = base * adj * user_scale,
        layout_dp = base * adj,
    }
    cache.W = w / cache.dp
    cache.H = h / cache.dp
    -- The screen at interface size 1. Layout choices (columns, margins, one
    -- or two column pages) are made on these, so a larger interface size
    -- draws the device's own layout bigger instead of a smaller device's.
    cache.LW = w / cache.layout_dp
    cache.LH = h / cache.layout_dp
    return cache
end

-- The screen width layout choices are made on, rounded so an exact 600 dp
-- screen is not 599.9994.
function BoardMetrics.layoutW()
    return math.floor(BoardMetrics.get().LW * 1000 + 0.5) / 1000
end

-- A pixel width in layout dp, for comparing a box against a layout
-- breakpoint.
function BoardMetrics.layoutDp(w_px)
    return w_px / BoardMetrics.get().layout_dp
end

-- dp to pixels at interface size 1, for layout limits that must not move
-- with the interface size.
function BoardMetrics.lpx(n)
    if not n or n <= 0 then return 0 end
    return math.ceil(n * BoardMetrics.get().layout_dp - 1e-7)
end

function BoardMetrics.get()
    if not cache or cache.w ~= Screen:getWidth() or cache.h ~= Screen:getHeight() then
        BoardMetrics.refresh()
    end
    return cache
end

-- dp to device pixels. Rounded up so a positive size never collapses to 0.
function BoardMetrics.px(n)
    if not n or n <= 0 then return 0 end
    return math.ceil(n * BoardMetrics.get().dp - 1e-7)
end

-- The pixel size of a font of nominal size n.
function BoardMetrics.fs(n)
    return BoardMetrics.px(n)
end

-- KOReader applies Screen:scaleBySize inside Font:getFace, so named and file
-- faces receive only BookOrbit's density and interface-size multipliers.
function BoardMetrics.fontSize(n)
    local metrics = BoardMetrics.get()
    return n * metrics.adj * metrics.scale
end

function BoardMetrics.namedFace(name, n)
    return Font:getFace(name, BoardMetrics.fontSize(n))
end

function BoardMetrics.fileFace(file, n)
    return Font:getFace(file, BoardMetrics.fontSize(n))
end

-- Single-line text height (TextWidget) for nominal size n.
function BoardMetrics.lh1(n)
    return math.ceil(FACE_HEIGHT_EM * BoardMetrics.fs(n) - 1e-7)
end

-- Line height of a TextBoxWidget for nominal size n.
function BoardMetrics.lh(n)
    return math.floor(1.3 * BoardMetrics.fs(n) + 0.5)
end

-- Height of an n-line TextBoxWidget of nominal size: lines of round(1.3 em)
-- plus the amount Noto's 1.362 em glyph box overflows one line, which
-- TextBoxWidget adds below the last line. One line is a TextWidget instead.
function BoardMetrics.textBoxHeight(lines, n)
    if lines <= 1 then return BoardMetrics.lh1(n) end
    local line = BoardMetrics.lh(n)
    local face = FACE_HEIGHT_EM * BoardMetrics.fs(n)
    return lines * line + math.max(0, -math.floor(line - face))
end

-- A face at nominal size n. Font:getFace applies scaleBySize itself, so it is
-- given the size with only the density step applied.
function BoardMetrics.face(n, kind)
    local file = FACES[kind or "sans"] or FACES.sans
    return BoardMetrics.fileFace(file, n)
end

-- Rebuilds without a screen, for specs.
function BoardMetrics._setForTest(values)
    cache = values
end

return BoardMetrics